{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5.10 批量归一化"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5.10.2 从零开始实现"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import time\n",
    "import torch\n",
    "from torch import nn, optim\n",
    "import torch.nn.functional as F\n",
    "\n",
    "import sys\n",
    "import d2lzh_pytorch as d2l\n",
    "device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "def batch_norm(is_training, X, gamma, beta, moving_mean,\n",
    "              moving_var, eps, momentum):\n",
    "    if not is_training:\n",
    "        X_hat = (X - moving_mean) / torch.sqrt(moving_var + eps)\n",
    "    else:\n",
    "        assert len(X.shape) in (2, 4)\n",
    "        if len(X.shape) == 2:\n",
    "            mean = X.mean(dim=0)\n",
    "            var = ((X - mean) ** 2).mean(dim=0)\n",
    "        else:\n",
    "            mean = X.mean(dim=0, keepdim=True).mean(dim=2,\n",
    "                    keepdim=True).mean(dim=3, keepdim=True)\n",
    "            var = ((X - mean) ** 2).mean(dim=0,\n",
    "                    keepdim=True).mean(dim=2, keepdim=True).mean(dim=3, \n",
    "                    keepdim=True)\n",
    "        # 训练模式下用当前的均值和方差做标准化\n",
    "        X_hat = (X - mean) / torch.sqrt(var + eps)\n",
    "        moving_mean = momentum * moving_mean + (1.0 - momentum) * mean\n",
    "        moving_var = momentum * moving_var + (1.0 - momentum) * var\n",
    "    Y = gamma * X_hat + beta    # 拉伸和偏移\n",
    "    return Y, moving_mean, moving_var"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "class BatchNorm(nn.Module):\n",
    "    def __init__(self, num_features, num_dims):\n",
    "        super(BatchNorm, self).__init__()\n",
    "        if num_dims == 2:\n",
    "            shape = (1, num_features)\n",
    "        else:\n",
    "            shape = (1, num_features, 1, 1)\n",
    "        self.gamma = nn.Parameter(torch.ones(shape))\n",
    "        self.beta = nn.Parameter(torch.zeros(shape))\n",
    "        self.moving_mean = torch.zeros(shape)\n",
    "        self.moving_var = torch.zeros(shape)\n",
    "        \n",
    "    def forward(self, X):\n",
    "        if self.moving_mean.device != X.device:\n",
    "            self.moving_mean = self.moving_mean.to(X.device)\n",
    "            self.moving_var = self.moving_var.to(X.device)\n",
    "        Y, self.moving_mean, self.moving_var = batch_norm(\n",
    "            self.training, X, self.gamma, self.beta, self.moving_mean,\n",
    "            self.moving_var, eps=1e-5, momentum=0.9)\n",
    "        return Y"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "net = nn.Sequential(\n",
    "    nn.Conv2d(1, 6, 5),    # in_channels, out_channels, kernel_size\n",
    "    BatchNorm(6, num_dims=4),\n",
    "    nn.Sigmoid(),\n",
    "    nn.MaxPool2d(2, 2),    # kernel_size, stride\n",
    "    nn.Conv2d(6, 16, 5),\n",
    "    BatchNorm(16, num_dims=4),\n",
    "    nn.Sigmoid(),\n",
    "    nn.MaxPool2d(2, 2),\n",
    "    d2l.FlattenLayer(),\n",
    "    nn.Linear(16 * 4 * 4, 120),\n",
    "    BatchNorm(120, num_dims=2),\n",
    "    nn.Sigmoid(),\n",
    "    nn.Linear(120, 84),\n",
    "    BatchNorm(84, num_dims=2),\n",
    "    nn.Sigmoid(),\n",
    "    nn.Linear(84, 10)\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "training on  cuda\n",
      "epoch 1, loss 0.5424, train acc 0.827, test acc 0.857, time 32.7 sec\n",
      "epoch 2, loss 0.3704, train acc 0.867, test acc 0.874, time 34.5 sec\n",
      "epoch 3, loss 0.3403, train acc 0.878, test acc 0.881, time 32.5 sec\n",
      "epoch 4, loss 0.3195, train acc 0.884, test acc 0.882, time 32.5 sec\n",
      "epoch 5, loss 0.3036, train acc 0.889, test acc 0.884, time 32.3 sec\n"
     ]
    }
   ],
   "source": [
    "batch_size = 16    # 256\n",
    "train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size=batch_size)\n",
    "lr, num_epochs = 1e-3, 5\n",
    "optimizer = torch.optim.Adam(net.parameters(), lr=lr)\n",
    "d2l.train_ch5(net, train_iter, test_iter, batch_size, optimizer,\n",
    "             device, num_epochs)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(tensor([0.8523, 1.5114, 1.2619, 1.5715, 1.5503, 1.3498], device='cuda:0',\n",
       "        grad_fn=<ViewBackward>),\n",
       " tensor([-0.4920,  0.0199,  0.7530,  1.0862, -1.7790, -0.1765], device='cuda:0',\n",
       "        grad_fn=<ViewBackward>))"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "net[1].gamma.view((-1,)), net[1].beta.view((-1,))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5.10.3 简洁实现"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "net = nn.Sequential(\n",
    "    nn.Conv2d(1, 6, 5),    # in_channels, out_channels, kernel_size\n",
    "    nn.BatchNorm2d(6),\n",
    "    nn.Sigmoid(),\n",
    "    nn.MaxPool2d(2, 2),    # kernel_size, stride\n",
    "    nn.Conv2d(6, 16, 5),\n",
    "    nn.BatchNorm2d(16),\n",
    "    nn.Sigmoid(),\n",
    "    nn.MaxPool2d(2, 2),\n",
    "    d2l.FlattenLayer(),\n",
    "    nn.Linear(16*4*4, 120),\n",
    "    nn.BatchNorm1d(120),\n",
    "    nn.Sigmoid(),\n",
    "    nn.Linear(120, 84),\n",
    "    nn.BatchNorm1d(84),\n",
    "    nn.Sigmoid(),\n",
    "    nn.Linear(84, 10)\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "training on  cuda\n",
      "epoch 1, loss 0.5416, train acc 0.827, test acc 0.858, time 24.1 sec\n",
      "epoch 2, loss 0.3719, train acc 0.869, test acc 0.866, time 23.6 sec\n",
      "epoch 3, loss 0.3362, train acc 0.880, test acc 0.883, time 26.1 sec\n",
      "epoch 4, loss 0.3191, train acc 0.885, test acc 0.880, time 25.2 sec\n",
      "epoch 5, loss 0.3010, train acc 0.890, test acc 0.881, time 25.1 sec\n"
     ]
    }
   ],
   "source": [
    "batch_size = 16    # 256\n",
    "train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size=batch_size)\n",
    "\n",
    "lr, num_epochs = 1e-3, 5\n",
    "optimizer = torch.optim.Adam(net.parameters(), lr=lr)\n",
    "d2l.train_ch5(net, train_iter, test_iter, batch_size, optimizer, \n",
    "              device, num_epochs)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
